package org.example.physics_engine.geometry.shapes;

import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.physics_engine.body.Mass;
import org.example.physics_engine.body.Transform;
import org.example.physics_engine.geometry.*;

@Getter
@NoArgsConstructor
public class Polygon extends AbstractShape{

    //多边形顶点数组
    protected Vector2d[] vertices;
    //多边形的法向量数组
    protected Vector2d[] normalVectors;

    public Polygon(Transform transform, Vector2d[] vertices, Vector2d[] normalVectors) {
        super(transform);
        this.vertices = vertices;
        this.normalVectors = normalVectors;
    }
    public Polygon(Transform transform, Vector2d[] vertices) {
        this(transform, vertices, Geometry.getCounterClockwiseEdgeNormals(vertices));
    }
    @Override
    public void zoom(int rate) {
        if (rate < 1) {
            //不能缩小到1以下
            return;
        }
        //调整顶点
        for (int i = 0; i < vertices.length; i++) {
            vertices[i] = vertices[i].multiplyNum((double) rate / this.rate);
        }
        for(Vector2d vertice : vertices) {
            System.out.println(vertice.x + ", " + vertice.y);
        }
    }

    @Override
    public AxisAlignBoundingBox createAABB() {
        //获取本shape旋转平移到哪里去了
        Vector2d p = transform.getTransformed(this.vertices[0]);
        double minX = p.x;
        double maxX = p.x;
        double minY = p.y;
        double maxY = p.y;
        //确定远的那两个框顶点
        for (int i = 1; i < this.vertices.length; i++) {
            double px = transform.getTransformedX(this.vertices[i]);
            double py = transform.getTransformedY(this.vertices[i]);
            minX = Math.min(px, minX);
            minY = Math.min(py, minY);
            maxX = Math.max(px, maxX);
            maxY = Math.max(py, maxY);
        }
        return new AxisAlignBoundingBox(minX, minY, maxX, maxY);
    }

    @Override
    public Interval project(Vector2d axis) {
        double v;
        Vector2d p = transform.getTransformed(this.vertices[0]);
        double min = axis.dotProduct(p);
        double max = min;
        int size = this.vertices.length;
        for (int i = 1; i < size; i++) {
            p = transform.getTransformed(this.vertices[i]);
            v = axis.dotProduct(p);
            min = Math.min(min, v);
            max = Math.max(max, v);
        }
        return new Interval(min, max);
    }

    @Override
    public Mass createMass(double density) {
        //利用密度和本多边形的顶点来算质量
        double INV_3 = 1.0 / 3.0;
        Vector2d center = new Vector2d();
        // 总面积
        double area = 0.0;
        // 转动惯量
        double I = 0.0;
        int n = this.vertices.length;
        // 获得平均中点
        Vector2d ac = new Vector2d();
        for (Vector2d vertex : this.vertices) {
            ac.add(vertex);
        }
        ac.divideNum(n);

        for (int i1 = n - 1, i2 = 0; i2 < n; i1 = i2++) {
            Vector2d p1 = this.vertices[i1];
            Vector2d p2 = this.vertices[i2];
            p1 = p1.difference(ac);
            p2 = p2.difference(ac);
            // 使用叉乘计算顶点和平均中点围成的的三角形面积
            double D = p1.crossProduct(p2);
            double triangleArea = 0.5 * D;
            area += triangleArea;
            // 计算每个三角形重心的横（纵）坐标
            // 并与该三角形的面积作乘积
            // 最终将每个乘积加和与总面积作除
            // 即为质心的横（纵）坐标
            center.x += (p1.x + p2.x) * INV_3 * triangleArea;
            center.y += (p1.y + p2.y) * INV_3 * triangleArea;

            I += triangleArea * (p2.dotProduct(p2) + p2.dotProduct(p1) + p1.dotProduct(p1));
        }
        area = Math.abs(area);
        center.divideNum(area);
        // 回到世界坐标系
        Vector2d c = center.sum(ac);

        // 总质量
        double m = density * area;
        I *= (density / 6.0);
        I -= m * center.getMagnitudeSquared();

        return new Mass(c, m, I);
    }

    @Override
    public Vector2d[] getFocus() {
        //多边形无焦点
        return null;
    }

    //如果给出了焦点，本方法将返回该{@link Shape} 的沃罗诺伊区域的分离轴线
    @Override
    public Vector2d[] getAxes(Vector2d[] focus) {
        //基于另一个图形的焦点获取分离轴
        int focusSize = focus != null ? focus.length : 0;
        int size = this.vertices.length;
        Vector2d[] axes = new Vector2d[size + focusSize];
        int index = 0;
        for (int i = 0; i < size; i++) {
            axes[index++] = transform.getTransformedRotate(this.normalVectors[i]);
        }
        for (int i = 0; i < focusSize; i++) {
            Vector2d f = focus[i];
            Vector2d closest = transform.getTransformed(this.vertices[0]);
            double d = f.distanceSquared(closest);
            for (int j = 1; j < size; j++) {
                Vector2d p = transform.getTransformed(this.vertices[j]);
                double dt = f.distanceSquared(p);
                if (dt < d) {
                    closest = p;
                    d = dt;
                }
            }
            Vector2d axis = f.to(closest);
            axis.normalize();
            axes[index++] = axis;
        }
        return axes;
    }

    @Override
    public Vector2d getFarthestFeature(Vector2d vector) {
        // 多边形没有PointFeature
        return null;
    }

    @Override
    public Vector2d getFarthestPoint(Vector2d vector) {
        Vector2d localn = transform.getInverseTransformedRotate(vector);
        int index = getFarthestVertexIndex(localn);
        return transform.getTransformed(this.vertices[index]);
    }

    //获取最远顶点索引
    int getFarthestVertexIndex(Vector2d vector) {
        int maxIndex = 0;
        int n = this.vertices.length;
        double max = vector.dotProduct(this.vertices[0]), candidateMax;

        if (max < (candidateMax = vector.dotProduct(this.vertices[1]))) {
            do {
                max = candidateMax;
                maxIndex++;
            } while ((maxIndex + 1) < n && max < (candidateMax = vector.dotProduct(this.vertices[maxIndex + 1])));
        } else if (max < (candidateMax = vector.dotProduct(this.vertices[n - 1]))) {
            maxIndex = n;
            do {
                max = candidateMax;
                maxIndex--;
            } while (maxIndex > 0 && max <= (candidateMax = vector.dotProduct(this.vertices[maxIndex - 1])));
        }
        return maxIndex;
    }
}
